package com.keio.handler;

import com.keio.common.codec.ProxyMessageDecoder;
import com.keio.common.codec.ProxyMessageEncoder;
import com.keio.common.config.ConfigParser;
import com.keio.common.protocol.ProxyMessage;
import com.keio.helper.ClientBootstrapHelper;
import io.netty.channel.*;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Description 建立与服务器的连接  处理收到的来自服务器的数据
 * @author KEIO  on 2022/1/14.
 * @version 1.0
 */
@Slf4j
public class ClientHandler extends ChannelInboundHandlerAdapter {

    /**
     * 全局管理channels
     */
    private ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    //serverPort与localPort映射map
    private ConcurrentHashMap<Integer,Integer> portMap = new ConcurrentHashMap<>();
    //所有localChannel共享，减少线程上下文切换
    private EventLoopGroup localGroup = new NioEventLoopGroup();
    //每个外部请求channelId与其处理器handler的映射关系
    private ConcurrentHashMap<String,LocalHandler> localHandlerMap = new ConcurrentHashMap<>();

    private ChannelHandlerContext ctx = null;
    public ChannelHandlerContext getCtx() {
        return ctx;
    }


    /**
     * @Description 建立连接发送注册请求
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ProxyMessage proxyMessage = new ProxyMessage();
        proxyMessage.setType(ProxyMessage.TYPE_REGISTER);
        HashMap<String, Object> metaData = new HashMap<String ,Object>();

        String clientKey = (String)ConfigParser.get("client-key");
        metaData.put("clientKey", clientKey);

        /*
        从配置文件中 得到服务器 和 客户端端口的对应关系
         */
        ArrayList<Integer> serverPortArr  = new ArrayList<Integer>();

        for(Map<String, Object> item: ConfigParser.getPortArray()) {
            serverPortArr.add((Integer) item.get("server-port"));
            portMap.put((Integer) item.get("server-port"), (Integer) item.get("client-port"));

        }
        metaData.put("ports", serverPortArr);

        proxyMessage.setMetaData(metaData);
        ctx.writeAndFlush(proxyMessage);

        log.info("收到服务器的连接建立, 发送注册请求");




    }


    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        channels.close();
        localGroup.shutdownGracefully();
        log.info(" 与服务器连接建立断开");
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ProxyMessage proxyMessage = (ProxyMessage) msg;
        int type = proxyMessage.getType();
        switch (type) {
            case ProxyMessage.TYPE_AUTH: {
                processAuth(proxyMessage);
                break;
            }

            case ProxyMessage.TYPE_CONNECTED: {
                processConnected(proxyMessage);
                break;
            }

            case ProxyMessage.TYPE_DISCONNECTED: {
                processDisconnected(proxyMessage);
                break;
            }

            case ProxyMessage.TYPE_KEEPALIVE: {

                break;
            }

            case ProxyMessage.TYPE_DATA: {
                processData(proxyMessage);
                break;
            }

        }

    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        super.exceptionCaught(ctx, cause);
    }


    /**
     * @Description 执行将服务器发送的数据  转发到内网客户端对应的端口上
     * @param proxyMessage
     */
    public void processData(ProxyMessage proxyMessage) {
        if(proxyMessage.getData() == null || proxyMessage.getData().length <= 0) {
            return;
        }
        String channelId = (String) proxyMessage.getMetaData().get("channelId");
        LocalHandler localHandler = localHandlerMap.get(channelId);

        localHandler.getLocalCtx().writeAndFlush(proxyMessage);
        log.info("从服务器收到数据, 长度为{} 字节, 转发到端口{}", proxyMessage.getData().length, localHandler
                .getLocalCtx().channel().remoteAddress());


    }

    public void processAuth(ProxyMessage proxyMessage) {
        Boolean flag = (Boolean) proxyMessage.getMetaData().get("isSuccess");

        if(flag) {
            log.info("与服务端 连接建立成功");
        } else {
            /*
            认证异常处理逻辑
             */
            log.info("服务端返回 客户端注册失败");
        }



    }

    /**
     * @Description 服务器通知 客户端建立相应连接
     * @param proxyMessage
     */
    public void processConnected(ProxyMessage proxyMessage){
        String channelId = proxyMessage.getMetaData().get("channelId").toString();
        Integer remotePort = (Integer) proxyMessage.getMetaData().get("remotePort");
        ClientBootstrapHelper localHelper = new ClientBootstrapHelper();
        ClientHandler clientHandler = this;
        ChannelInitializer channelInitializer = new ChannelInitializer() {
            @Override
            protected void initChannel(Channel channel) throws Exception {
                LocalHandler localHandler = new LocalHandler(clientHandler, channelId);
                channel.pipeline().addLast(
                        new ProxyMessageEncoder(),
                        new ProxyMessageDecoder(),
                        localHandler
                );
                channels.add(channel);
                localHandlerMap.put(channelId, localHandler);

            }
        };
        String localhost = ConfigParser.get("local-host").toString();
        int localPort =  portMap.get(remotePort);
        localHelper.start(localGroup, channelInitializer, localhost, localPort);

        log.info("服务器端口{} 请求建立连接  本地端口{} 连接建立", remotePort, localPort);


    }


    public void processDisconnected(ProxyMessage proxyMessage){
        String channelId = proxyMessage.getMetaData().get("channelId").toString();
        LocalHandler localHandler = localHandlerMap.get(channelId);
        localHandler.getLocalCtx().channel().close();

        log.info("对应本地端口关闭 , channelId为 {}" , channelId);


    }



}
